package uz.rt.physics;

import uz.rt.math.Interpolator;
import uz.rt.utils.Itemizable;
import uz.rt.utils.Pair;

import java.util.ArrayList;
import java.util.Collections;
import java.util.ListIterator;

/**
 * User: Wacko von Belsebub
 * Date: 10/30/11
 * Time: 8:51 PM
 */
public class Curve<K extends Comparable, V extends Comparable> implements Itemizable<Integer, Pair<K, V>>
{
// ------------------------------ FIELDS ------------------------------

    protected final ArrayList<Pair<K, V>> a = new ArrayList<Pair<K, V>>();
    private boolean sorted = false;

// --------------------------- CONSTRUCTORS ---------------------------

    public Curve()
    {
    }

// --------------------- GETTER / SETTER METHODS ---------------------

    public boolean isSorted()
    {
        return sorted;
    }

// ------------------------ CANONICAL METHODS ------------------------


    @Override
    public boolean equals(Object o)
    {
        if (this == o) return true;
        if (!(o instanceof Curve)) return false;

        Curve curve = (Curve) o;

        return sorted == curve.sorted && a.equals(curve.a);

    }

    @Override
    public int hashCode()
    {
        int result = a.hashCode();
        result = 31 * result + (sorted ? 1 : 0);
        return result;
    }

    @Override
    public String toString()
    {
        sort();
        StringBuilder sb = new StringBuilder();
        for (Pair<K, V> anA : a){
            sb.append(anA);
            sb.append(System.getProperty("line.separator"));
        }
        return sb.toString();
    }

// ------------------------ INTERFACE METHODS ------------------------


// --------------------- Interface Itemizable ---------------------

    public void add(final Pair<K, V> p)
    {
        a.add(p);
        sorted = false;
    }

    public void edit(Integer idx, Pair<K, V> pair)
    {
        if (idx < 0) throw new ArrayIndexOutOfBoundsException("Cannot edit coordinate : index out of bound (<0)");
        else if (idx > a.size() - 1)
            throw new ArrayIndexOutOfBoundsException("Cannot edit coordinate : index out of bound (>dimension)");
        else
        {
            a.set(idx, pair);
            sorted = false;
        }
    }

    public void removeByKey(Integer idx)
    {
        if (idx < 0) throw new ArrayIndexOutOfBoundsException("Cannot edit coordinate : index out of bound (<0)");
        else if (idx > a.size() - 1)
            throw new ArrayIndexOutOfBoundsException("Cannot edit coordinate : index out of bound (>dimension)");
        else a.remove(idx.intValue());
    }

    public void removeValue(Pair<K, V> item)
    {
        ListIterator<Pair<K, V>> itr = a.listIterator();
        while (itr.hasNext())
        {
            Pair<K, V> p = itr.next();
            if (p.equals(item)) itr.remove();
        }
    }

// -------------------------- OTHER METHODS --------------------------

    /**
     * Add a data point x,y to the curve
     *
     * @param x variable x
     * @param y variable y
     */
    public void add(final K x, final V y)
    {
        a.add(new Pair<K, V>(x, y));
        sorted = false;
    }

    /**
     * Get the data point at index location idx
     *
     * @param idx Index location
     * @return Data point
     */
    public Pair<K, V> at(final int idx)
    {
        return a.get(idx);
    }

    /**
     * Replace the data point at position ox with a new data point.
     *
     * @param ox Original x coordinate at which the data must be changed
     * @param p  New coordinate replacing the old one
     */
    public void edit(final K ox, final Pair<K, V> p)
    {
        for (int i = 0; i < a.size(); i++)
        {
            if (a.get(i).getX().equals(ox))
            {
                a.set(i, p);
                sorted = false;
            }
        }
    }

    /**
     * Replace the data point at position ox with a new data point.
     *
     * @param ox Original x coordinate at which the data must be changed
     * @param x  New x coordinate replacing the old one
     * @param y  New y coordinate replacing the old one
     */
    public void edit(final K ox, final K x, final V y)
    {
        for (int i = 0; i < a.size(); i++)
        {
            if (a.get(i).getX().equals(ox))
            {
                a.set(i, new Pair<K, V>(x, y));
                sorted = false;
            }
        }
    }

    /**
     * Returns the function result of a given x value..
     *
     * @param x variable for which y is determined. The variable must be a value between the minimum and maximum x coordinate, otherwise and ArithmeticException is thrown. If the exact value is not found, an interpolated y is calculated.
     * @return The function result of the variable x
     * @throws ArithmeticException Thrown if the x variable is outside the x range.
     */
    public double get(final K x) throws ArithmeticException
    {
        if (!sorted) this.sort();
        for (Pair<K, V> anA : a) //noinspection NumberEquality
            if (anA.getX() == x) return (Double) anA.getY();
        int i0 = 0;
        int i1 = a.size() - 1;
        double dx = (Double) x;
        double ld = (Double) a.get(i0).getX();
        double ud = (Double) a.get(i1).getX();
        if (dx < ld) throw new ArithmeticException(
                "argument " + String.valueOf(dx) + " is smaller then lowest x value (" + ld + ") in the curve");
        if (dx > ud) throw new ArithmeticException(
                "argument " + String.valueOf(dx) + " is bigger then largest x value (" + ud + ") in the curve");
        for (int i = 1; i < a.size(); i++)
        {
            double d = (Double) a.get(i).getX();
            if (ld < d && d < dx)
            {
                i0 = i;
                ld = d;
            }
            if (ud > d && d > dx)
            {
                i1 = i;
                ud = d;
            }
        }
        double x0 = (Double) a.get(i0).getX();
        double x1 = (Double) a.get(i1).getX();
        double y0 = (Double) a.get(i0).getY();
        double y1 = (Double) a.get(i1).getY();
        return Interpolator.linear(dx, x0, x1, y0, y1);
    }

    /**
     * Sort the data points
     */
    public void sort()
    {
        Collections.sort(a);
        sorted = true;
    }

    /**
     * Checks if the curve has any data points
     *
     * @return True if there are no data points stored in the class, false if not.
     */
    public boolean isEmpty()
    {
        return a.isEmpty();
    }

    /**
     * Remove a data point(s) which have x as there first value
     *
     * @param x The data point value x that has to be removed
     */
    public void remove(final K x)
    {
        ListIterator<Pair<K, V>> itr = a.listIterator();
        while (itr.hasNext())
        {
            Pair<K, V> p = itr.next();
            if (p.getX().equals(x)) itr.remove();
        }
    }

    /**
     * Remove all data points from the curve
     */
    public void removeAll()
    {
        a.clear();
    }

    /**
     * Get the number of data points in the curve
     *
     * @return Number of data points in the curve
     */
    public int size()
    {
        return a.size();
    }
}
